import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import serial
import serial.tools.list_ports
import threading
import queue
import time
import os


class SerialMonitorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("双串口数据接收工具")
        self.root.geometry("900x700")
        self.root.resizable(True, True)

        # 创建串口配置帧
        self.setup_ui()

        # 串口相关变量
        self.ser1 = None
        self.ser2 = None
        self.serial_thread1 = None
        self.serial_thread2 = None
        self.running1 = False
        self.running2 = False
        self.data_queue1 = queue.Queue()
        self.data_queue2 = queue.Queue()
        self.file1 = None
        self.file2 = None

        # 启动UI更新循环
        self.root.after(100, self.update_ui)

    def setup_ui(self):
        # 串口1配置
        frame1 = ttk.LabelFrame(self.root, text="串口1 配置", padding=10)
        frame1.grid(row=0, column=0, padx=10, pady=10, sticky="ew")

        # 串口2配置
        frame2 = ttk.LabelFrame(self.root, text="串口2 配置", padding=10)
        frame2.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

        # 配置公共参数
        params = [
            ("端口:", "port1", frame1),
            ("波特率:", "baudrate1", frame1),
            ("端口:", "port2", frame2),
            ("波特率:", "baudrate2", frame2)
        ]

        self.create_combos(params)

        # 按钮区域
        btn_frame = ttk.Frame(self.root)
        btn_frame.grid(row=1, column=0, columnspan=2, pady=10)

        self.start_btn1 = ttk.Button(btn_frame, text="启动串口1", command=self.toggle_serial1)
        self.start_btn1.grid(row=0, column=0, padx=5)

        self.start_btn2 = ttk.Button(btn_frame, text="启动串口2", command=self.toggle_serial2)
        self.start_btn2.grid(row=0, column=1, padx=5)

        self.save_btn = ttk.Button(btn_frame, text="保存数据", command=self.save_data)
        self.save_btn.grid(row=0, column=2, padx=5)

        self.clear_btn = ttk.Button(btn_frame, text="清除显示", command=self.clear_display)
        self.clear_btn.grid(row=0, column=3, padx=5)

        # 数据显示区
        display_frame = ttk.Frame(self.root)
        display_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")

        self.text1 = scrolledtext.ScrolledText(display_frame, width=55, height=20)
        self.text1.grid(row=0, column=0, padx=5, sticky="nsew")

        self.text2 = scrolledtext.ScrolledText(display_frame, width=55, height=20)
        self.text2.grid(row=0, column=1, padx=5, sticky="nsew")

        # 状态栏
        self.status_var1 = tk.StringVar(value="串口1状态: 未连接")
        self.status_var2 = tk.StringVar(value="串口2状态: 未连接")

        status_bar = ttk.Frame(self.root)
        status_bar.grid(row=3, column=0, columnspan=2, sticky="ew", padx=10, pady=10)

        ttk.Label(status_bar, textvariable=self.status_var1).grid(row=0, column=0, sticky="w")
        ttk.Label(status_bar, textvariable=self.status_var2).grid(row=0, column=1, sticky="e")

        # 设置网格可调整大小
        self.root.columnconfigure(0, weight=1)
        self.root.columnconfigure(1, weight=1)
        self.root.rowconfigure(2, weight=1)

    def create_combos(self, params):
        # 获取可用串口列表
        ports = [port.device for port in serial.tools.list_ports.comports()]
        baudrates = ['9600', '19200', '38400', '57600', '115200', '230400', '460800', '921600']

        for label, name, frame in params:
            row = ttk.Frame(frame)
            row.pack(fill="x", pady=5)

            ttk.Label(row, text=label, width=8).pack(side="left")

            if "port" in name:
                combo = ttk.Combobox(row, values=ports, width=15)
                if ports:
                    combo.set(ports[0])
            else:
                combo = ttk.Combobox(row, values=baudrates, width=10)
                combo.set('115200')

            combo.pack(side="left", padx=5)
            setattr(self, name, combo)

    def toggle_serial1(self):
        if self.running1:
            self.stop_serial1()
        else:
            self.start_serial1()

    def toggle_serial2(self):
        if self.running2:
            self.stop_serial2()
        else:
            self.start_serial2()

    def start_serial1(self):
        port = self.port1.get()
        baudrate = int(self.baudrate1.get())

        try:
            self.ser1 = serial.Serial(port, baudrate, timeout=1)
            self.running1 = True
            self.serial_thread1 = threading.Thread(target=self.read_serial, args=(self.ser1, self.data_queue1, 1))
            self.serial_thread1.daemon = True
            self.serial_thread1.start()

            self.status_var1.set(f"串口1状态: 已连接 ({port} @ {baudrate})")
            self.start_btn1.config(text="停止串口1")
        except Exception as e:
            messagebox.showerror("错误", f"无法打开串口1: {str(e)}")

    def start_serial2(self):
        port = self.port2.get()
        baudrate = int(self.baudrate2.get())

        try:
            self.ser2 = serial.Serial(port, baudrate, timeout=1)
            self.running2 = True
            self.serial_thread2 = threading.Thread(target=self.read_serial, args=(self.ser2, self.data_queue2, 2))
            self.serial_thread2.daemon = True
            self.serial_thread2.start()

            self.status_var2.set(f"串口2状态: 已连接 ({port} @ {baudrate})")
            self.start_btn2.config(text="停止串口2")
        except Exception as e:
            messagebox.showerror("错误", f"无法打开串口2: {str(e)}")

    def stop_serial1(self):
        self.running1 = False
        if self.serial_thread1 and self.serial_thread1.is_alive():
            self.serial_thread1.join(0.5)

        if self.ser1 and self.ser1.is_open:
            self.ser1.close()

        self.status_var1.set("串口1状态: 未连接")
        self.start_btn1.config(text="启动串口1")

    def stop_serial2(self):
        self.running2 = False
        if self.serial_thread2 and self.serial_thread2.is_alive():
            self.serial_thread2.join(0.5)

        if self.ser2 and self.ser2.is_open:
            self.ser2.close()

        self.status_var2.set("串口2状态: 未连接")
        self.start_btn2.config(text="启动串口2")

    def read_serial(self, ser, data_queue, port_num):
        while getattr(self, f"running{port_num}"):
            try:
                if ser.in_waiting:
                    data = ser.read(ser.in_waiting)
                    decoded_data = data.decode('utf-8', errors='replace')
                    data_queue.put(decoded_data)

                    # 保存到文件（如果打开）
                    file = getattr(self, f"file{port_num}")
                    if file:
                        file.write(decoded_data)
                        file.flush()
                time.sleep(0.01)
            except Exception as e:
                print(f"读取串口{port_num}出错: {str(e)}")
                break

    def update_ui(self):
        # 处理串口1数据
        while not self.data_queue1.empty():
            data = self.data_queue1.get()
            self.text1.insert(tk.END, data)
            self.text1.see(tk.END)

        # 处理串口2数据
        while not self.data_queue2.empty():
            data = self.data_queue2.get()
            self.text2.insert(tk.END, data)
            self.text2.see(tk.END)

        self.root.after(50, self.update_ui)

    def save_data(self):
        if not self.file1:
            filename1 = filedialog.asksaveasfilename(
                title="保存串口1数据",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if filename1:
                self.file1 = open(filename1, 'a')

        if not self.file2:
            filename2 = filedialog.asksaveasfilename(
                title="保存串口2数据",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if filename2:
                self.file2 = open(filename2, 'a')

    def clear_display(self):
        self.text1.delete(1.0, tk.END)
        self.text2.delete(1.0, tk.END)

    def on_closing(self):
        # 关闭文件
        if self.file1:
            self.file1.close()
        if self.file2:
            self.file2.close()

        # 停止串口
        if self.running1:
            self.stop_serial1()
        if self.running2:
            self.stop_serial2()

        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    app = SerialMonitorApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()